import {Component, Input, OnInit} from '@angular/core';
import {UtilComponent} from '../../interface/util-component.interface';
import Map from 'ol/Map';
import Feature from 'ol/Feature.js';
import LineString from 'ol/geom/LineString.js';
import VectorLayer from 'ol/layer/Vector.js';
import VectorSource from 'ol/source/Vector.js';
import {transform} from 'ol/proj';

@Component({
    selector: 'kylin-fractal-render',
    templateUrl: './fractal-render.component.html',
    styleUrls: ['./fractal-render.component.less']
})
export class FractalRenderComponent implements OnInit, UtilComponent {
    // ---------- 接口实现 begin -------------- //
    hasContent = true;
    map: Map;

    @Input() set data(val) {
        Object.assign(this, val);
    };

    // ---------- 接口实现 end ---------------- //
    depth = 5;
    depthInfo: number;
    cos30: any;
    triangle: any;
    feature: any;
    layer: any;
    updateTimer: any;
    originView: any;

    constructor() {
    }

    ngOnInit() {
        this.originView = this.map.getView().getProperties();
        this.originView['zoom'] = this.map.getView().getZoom();
        this.turnOn();
    }

    turnOn() {
        const radius = 10e6;
        this.cos30 = Math.cos(Math.PI / 6);
        const sin30 = Math.sin(Math.PI / 6);
        const rise = radius * sin30;
        const run = radius * this.cos30;

        this.triangle = new LineString([
            [0, radius], [run, -rise], [-run, -rise], [0, radius]
        ]);

        this.feature = new Feature(this.triangle);

        this.layer = new VectorLayer({
            source: new VectorSource({
                features: [this.feature],
            }),
        });
        this.map.addLayer(this.layer);
        this.map.getView().setCenter([0, 0]);
        this.map.getView().setZoom(1);
        /*this.map.getView().setCenter([90, 40]);
        this.map.getView().setZoom(1);
        this.originView = {
            center:this.map.getView()
        }*/

        this.handlerDepthChange();
    }

    turnOff() {
        this.map.removeLayer(this.layer);
        if (this.updateTimer) {
            window.clearTimeout(this.updateTimer);
        }
        this.map.getView().setCenter(this.originView.center);
        this.map.getView().setZoom(this.originView.zoom);
    }

    cancel(options?: any): any {
        this.turnOff();
    }

    makeFractal = (depth) => {
        const geometry = this.triangle.clone();
        const graph = this.coordsToGraph(geometry.getCoordinates());
        for (let i = 0; i < depth; ++i) {
            let node = graph as any;
            while (node.next) {
                const next = node.next;
                this.injectNodes(node);
                node = next;
            }
        }
        const coordinates = this.graphToCoords(graph);
        // document.getElementById('count').innerHTML = coordinates.length;
        this.depthInfo = coordinates.length;
        const coordinates4326 = coordinates.map(cord => {
            return transform(cord, 'EPSG:3857', 'EPSG:3857');
        });
        geometry.setCoordinates(coordinates4326);
        this.feature.setGeometry(geometry);
    }

    injectNodes = (startNode) => {
        const endNode = startNode.next;

        const start = startNode.point;
        const end = startNode.next.point;
        const dx = end[0] - start[0];
        const dy = end[1] - start[1];

        // first point at 1/3 along the segment
        const firstNode = {
            point: [start[0] + dx / 3, start[1] + dy / 3]
        };

        // second point at peak of _/\_
        const r = Math.sqrt(dx * dx + dy * dy) / (2 * this.cos30);
        const a = Math.atan2(dy, dx) + Math.PI / 6;
        const secondNode = {
            point: [start[0] + r * Math.cos(a), start[1] + r * Math.sin(a)]
        };

        // third point at 2/3 along the segment
        const thirdNode = {
            point: [end[0] - dx / 3, end[1] - dy / 3]
        };

        (startNode as any).next = firstNode;
        (firstNode as any).next = secondNode;
        (secondNode as any).next = thirdNode;
        (thirdNode as any).next = endNode;
    }

    coordsToGraph = (coordinates) => {
        const graph = {
            point: coordinates[0]
        };
        const length = coordinates.length;
        for (let level = 0, node = graph as any; level < length - 1; ++level) {
            node.next = {
                point: coordinates[level + 1]
            };
            node = node.next;
        }
        return graph;
    }

    graphToCoords = (graph) => {
        const coordinates = [graph.point];
        for (let node = graph, i = 1; node.next; node = node.next, ++i) {
            coordinates[i] = node.next.point;
        }
        return coordinates;
    }
    update = () => {
        // makeFractal(Number(depthInput.value));
        this.makeFractal(+this.depth);
    }

    /**
     * Regenerate fractal on depth change.  Change events are debounced so updates
     * only occur every 200ms.
     */
    handlerDepthChange() {
        window.clearTimeout(this.updateTimer);
        this.updateTimer = window.setTimeout(this.update, 200);
    };
}
